package model.code.line;

import editor.NotImplementedException;
import event.*;
import lang.Encoder;
import lang.asm.ASM;
import lang.asm.ASMParse;
import lang.asm.DataTypeUtil;
import model.caret.TextCaret;
import model.code.Function;
import model.code.GlobalVariable;
import model.code.Structure;
import model.symbol.*;
import util.ArrayUtil;
import util.IntList;
import util.Pair;
import util.StringUtil;

import java.util.ArrayList;
import java.util.HashMap;

/**
 * This class represents a {@link Line} of assembly language code.
 *
 * @author Neil Dickson
 */
public class ASMLine extends Line {
	protected	HashMap<Register,DataType>	assumptions;

	protected	int			label;
	protected	int			prefix;
	protected	int			primaryKeyword;
	protected	int			segment;
	protected	int			commentStart;
	protected	IntList		operandIndices;
	protected	int			operandsEndIndex;

	private static final char[]	EXTRA_WORD_CHARS = {'_','$','@','?'};
	public static final HashMap<Register,DataType> DUMMY_ASSUMPTIONS = new HashMap<Register,DataType>(0);

	/**
	 * Constructor for an ASMLine
	 * @param list the list of lines
	 * @param text the line's text
	 * @param assumptions the current set of register assumptions
	 */
	public ASMLine(LineList list,char[] text,HashMap<Register,DataType> assumptions) {
		super(list,text);
		this.assumptions = assumptions;
		initializeLine();
	}
	protected void initializeLine() {
		ASMParse.instance.tokenizeLine(this);
		// Look for comment start
		commentStart = -1;
		for (int i=0;i<nReferences;++i) {
			if (isOperator(i,';')) {
				commentStart = i;
				break;
			}
		}
		// Look for line label
		label = -1;
		for (int i=0;i<nReferences-1 && i!=commentStart;++i) {	// -1 is because there must be a colon after it that we need to check for
			Object ref = references[i];
			if (ref!=WHITE_SPACE_MARKER && !(ref!=null && ref instanceof LineLabel && isOperator(i+1,':'))) {
				break;
			}
			if (ref!=WHITE_SPACE_MARKER) {
				label = i;
				// NOTE: This was added because some labels weren't being added to local scope until replacement, but remove it if it screws stuff up
				list.getParent().getScope().add((LineLabel)ref);
				break;
			}
		}
		// Look for any segment prefix (e.g. "mov ax,fs:[bx]", but not "mov fs,ax")
		// NOTE: Can only have up to one in a single instruction.
		segment = -1;
		for (int i=0;i<nReferences-1 && i!=commentStart;++i) {	// -1 is because there must be a colon after it that we need to check for, else it could be an operand on its own
			Object ref = references[i];
			if (ref!=null && ref instanceof Register && ((Register)ref).getType()==DataTypeUtil.TYPE_INDEX_SR && isOperator(i+1,':')) {
				segment = i;
				break;
			}
		}

		// Look for the primary keyword (a mnemonic or keyword macro)
		int i = (label>=0) ? label+2 : 0;
		int end = (commentStart>=0) ? commentStart : nReferences;
		int firstNonWhitespace = -1;
		prefix = -1;
		primaryKeyword = -1;
		for (;i<end;++i) {
			Object ref = references[i];
			if (ref!=WHITE_SPACE_MARKER && firstNonWhitespace<0) {
				firstNonWhitespace = i;
			}
			if (prefix<0 && (ref instanceof Mnemonic) && ((Mnemonic)ref).isPrefix(ASM.language.getScope())) {
				prefix = i;
			}
			else if ((ref instanceof Mnemonic && !(((Mnemonic)ref).isPrefix(ASM.language.getScope()))) || (ref instanceof Keyword && ((Keyword)ref).isLineStart())) {
				primaryKeyword = i;
				break;
			}
		}
		// If there isn't a valid primary keyword, allow unknown word to take its place
		if (primaryKeyword<0 && firstNonWhitespace>=0 && references[firstNonWhitespace]==null) {
			primaryKeyword = firstNonWhitespace;
		}

		if (primaryKeyword>=0 && references[primaryKeyword]==ASM.ASSUME) {
			this.assumptions = new HashMap<Register,DataType>(assumptions);
			for (int j=primaryKeyword+2;j<nReferences-2;++j) {	// +2 is for that there must be space after ASSUME, -2 is for that EAX:NOTHING is 3 Tokens
				// Check for <Register>:NOTHING or <Register>:PTR <DataType>
				if (references[j] instanceof Register && isOperator(j+1,':')) {
					if (references[j+2]==ASM.NOTHING) {
						this.assumptions.remove(references[j]);
					}
					else if (references[j+2]==ASM.PTR && (j+4<nReferences) && references[j+3]==WHITE_SPACE_MARKER && (references[j+4] instanceof DataType)) {
						this.assumptions.put((Register)references[j],(DataType)references[j+4]);
					}
				}
			}
		}

		operandIndices = new IntList(2);
		if (primaryKeyword>=0) {
			operandsEndIndex = primaryKeyword+1;
			for (i = primaryKeyword+1;i<nReferences && i!=commentStart;++i) {
				// The first non-whitespace token after the primary keyword is the first operand's start
				if (references[i]!=WHITE_SPACE_MARKER) {
					operandIndices.add(i);
					operandsEndIndex = i+1;
					break;
				}
			}
			for (;i<nReferences && i!=commentStart;++i) {
				if (isOperator(i,',')) {
					// The following token should be added as the operand start even if it doesn't exist, is the comment start, or is another comma, so that there is some marking of the comma here
					operandIndices.add(i+1);
				}
				if (references[i]!=WHITE_SPACE_MARKER) {
					operandsEndIndex = i+1;
				}
			}
		}

		updateErrors();
	}

	public TextReplacementEvent replace(int startCol, int endCol, String newText) {
		int lineIndex = list.indexOf(this);
		TextReplacementEvent textEvent = new TextReplacementEvent(list,new String(text,startCol,endCol-startCol),lineIndex,startCol,lineIndex,startCol+newText.length());
		boolean updateDone = false;
		int startingToken = getTokenAt(startCol);
		// Check for if we're just renaming something
		if (StringUtil.isAllWordChars(newText,EXTRA_WORD_CHARS)) {
			if (nReferences>0 && isDefiningReference[startingToken] && endCol<=getTokenColumn(startingToken+1)) {
				Object object = references[startingToken];
				if (object instanceof Symbol) {
					((Symbol)object).getName().replace(startCol-getTokenColumn(startingToken),endCol-getTokenColumn(startingToken),newText);
					textEvent.addReferences(Scope.getAddedRenameReferences((Symbol)object));
					updateDone = true;
				}
			}
			else if (startingToken>0 && startCol==getTokenColumn(startingToken) && nReferences>0 && isDefiningReference[startingToken-1] && endCol==startCol) {
				Object object = references[startingToken-1];
				if (object instanceof Symbol) {
					((Symbol)object).getName().append(newText);
					textEvent.addReferences(Scope.getAddedRenameReferences((Symbol) object));
					updateDone = true;
				}
			}
		}
		// If we're not just renaming something, do the whole shebang
		if (!updateDone) {
			boolean wasAssumeLine = isAssumeLine();
			LineLabel previousLabel = (label>=0) ? (LineLabel)references[label] : null;
			clearReferences();
			clearErrors();
			if (lineIndex>0) {
				assumptions = ((ASMLine)list.get(lineIndex-1)).assumptions;
			}
			else if (assumptions.size()>0) {
				assumptions.clear();
			}
			replaceJustText(startCol,endCol,newText.toCharArray());
			initializeLine();

			// Handle LineLabel scoping issues
			// NOTE: updateErrors() should get called automatically on removals and additions to the Scope
			if (previousLabel!=null) {
				if (!hasLabel()) {
					list.getParent().getScope().remove(previousLabel);
				}
				else if (getLabel()!=previousLabel) {
					// Get the label before removing the old one, because the line will update errors and no longer have a label.
					LineLabel newLabel = getLabel();
					list.getParent().getScope().remove(previousLabel);
					list.getParent().getScope().add(newLabel);
				}
			}
			else if (hasLabel()) {
				list.getParent().getScope().add(getLabel());
			}
			
			// The assumptions of all subsequent Lines in the LineList need to be reassessed after this.
			if (wasAssumeLine || isAssumeLine()) {
				HashMap<Register,DataType> newAssumptions = assumptions;
				for (int i=lineIndex+1;i<list.size();++i) {
					ASMLine line = (ASMLine)list.get(i);
					line.reassessAssumptions(newAssumptions);
					if (line.isAssumeLine()) {
						newAssumptions = line.getAssumptions();
					}
				}
			}
		}
		return textEvent;
	}

	/**
	 * Copies this line, possibly giving the new line a different {@link LineList}
	 * @param list either this Line's {@link LineList}, or a new one
	 * @param index the index at which the copied line will be placed later
	 * @return the new Line
	 */
	public ASMLine copy(LineList list,int index) {
		return new ASMLine(list, ArrayUtil.subarray(text,0,length),(index>0) ? ((ASMLine)list.get(index-1)).assumptions : new HashMap<Register, DataType>(1));
	}

	public HashMap<Register,DataType> getAssumptions() {
		return assumptions;
	}

	public int getPrimaryKeyword() {
		return primaryKeyword;
	}

	public IntList getOperandIndices() {
		return operandIndices;
	}
	public int getOperandsEndIndex() {
		return operandsEndIndex;
	}

	public boolean hasLabel() {
		return label>=0;
	}
	public LineLabel getLabel() {
		return (label>=0) ? (LineLabel)references[label] : null;
	}
	public boolean hasPrefix() {
		return prefix>=0;
	}
	public Mnemonic getPrefix() {
		return (prefix>=0) ? (Mnemonic)references[prefix] : null;
	}
	public boolean hasSegmentPrefix() {
		return segment>=0;
	}
	public Register getSegmentPrefix() {
		return (segment>=0) ? (Register)references[segment] : null;
	}
	public boolean isAssumeLine() {
		return primaryKeyword>=0 && references[primaryKeyword]==ASM.ASSUME;
	}
	public boolean hasComment() {
		return commentStart>=0;
	}
	public int getCommentStart() {
		return commentStart;
	}

	public void updateErrors() {
		// It's not worth checking for errors when there's no global scope yet.
		if (list==null || list.getParent().getGlobalScope()==null) {
			return;
		}

		nErrors = 0;
		int currentMode = list.getParent().getMode();
		// Check for unknown tokens
		for (int i=0;i<nReferences && i!=commentStart;++i) {
			if (references[i]==null) {
				addError(new SyntaxError(this,referenceIndices[i],getTokenColumn(i+1),"\""+new String(text,referenceIndices[i],getTokenColumn(i+1)-referenceIndices[i])+"\" isn't defined in this context",false));
			}
		}

		if (nErrors>0) {
			return;
		}

		if (label>=0) {
			Scope languageScope = ASM.language.getScope();
			Scope scope = list.getParent().getScope();
			String name = ((LineLabel)references[label]).getName().toString();
			if (languageScope.contains(name) && !name.equals(ASM.AT_AT.toString())) {
				addError(new SyntaxError(this, referenceIndices[label],referenceIndices[label+1],"\""+references[label]+"\" is already defined in the language scope, so it can't be redefined as a line label."));
			}
			else if (scope.getParent()!=null && scope.getParent().contains(name)) {
				ArrayList<Symbol> symbols = scope.getParent().get(name);
				addError(new SyntaxError(this, referenceIndices[label],referenceIndices[label+1],"\""+references[label]+"\" is already defined "+symbols.size()+" times in the global scope, so it can't be redefined as a line label."));
			}
			else if (name.length()==0) {
				addError(new SyntaxError(this, referenceIndices[label],referenceIndices[label+1],"Line label names can't be empty."));
			}
			else if ((name.charAt(0)>='0' && name.charAt(0)<='9') || name.charAt(0)=='.') {
				addError(new SyntaxError(this, referenceIndices[label],referenceIndices[label+1],"Line labels can't begin with numbers or \".\"."));
			}
			else if (!name.equals(ASM.AT_AT.toString())) {
				ArrayList<Symbol> symbols = scope.get(name);
				if (symbols!=null && symbols.size()>1) {
					addError(new SyntaxError(this, referenceIndices[label],referenceIndices[label+1],"There's at least one other symbol in this scope with this name."));
				}
			}
		}

		// Check if the primary keyword or prefix is the first non-whitespace token (after any label)
		int i = (label>=0) ? label+2 : 0;
		int end = (commentStart>=0) ? commentStart : nReferences;
		for (;i<end;++i) {
			Object ref = references[i];
			if (ref!=WHITE_SPACE_MARKER && i!=prefix && i!=primaryKeyword) {
				addError(new SyntaxError(this, referenceIndices[i],getTokenColumn(i+1),"Lines must start with a mnemonic or a macro (excluding an optional line label)."));
			}
			else if (i==primaryKeyword && prefix>=0) {
				Mnemonic prefixObject = (Mnemonic)references[prefix];
				ArrayList<String> allowedMnemonics = new ArrayList<String>();
				for (Object[] operandSet : prefixObject.getOperandTypes(currentMode)) {
					allowedMnemonics.add((String)operandSet[0]);
				}
				addError(new SyntaxError(this, referenceIndices[prefix],getTokenColumn(prefix+1),"\""+prefixObject+"\" is a prefix, so it must appear before a mnemonic.\nMnemonics it supports are: "+StringUtil.toCommaSpaceSeparatedList(allowedMnemonics)));
			}
			if (ref!=WHITE_SPACE_MARKER) {
				break;
			}
		}

		// Check for errors involving a prefix
		if (prefix>=0) {
			Mnemonic prefixObject = (Mnemonic)references[prefix];
			if (primaryKeyword<0 || references[primaryKeyword]==null || !(references[primaryKeyword] instanceof Mnemonic)) {
				ArrayList<String> allowedMnemonics = new ArrayList<String>();
				for (Object[] operandSet : prefixObject.getOperandTypes(currentMode)) {
					allowedMnemonics.add((String)operandSet[0]);
				}
				if (primaryKeyword<0) {
					addError(new SyntaxError(this, referenceIndices[prefix],getTokenColumn(prefix+1),"\""+prefixObject+"\" is a prefix, so it must be followed by a mnemonic.\nMnemonics it supports are: "+StringUtil.toCommaSpaceSeparatedList(allowedMnemonics)));
				}
				else if (references[primaryKeyword]==null || !(references[primaryKeyword] instanceof Mnemonic)) {
					addError(new SyntaxError(this, referenceIndices[primaryKeyword],getTokenColumn(primaryKeyword+1),"\""+prefixObject+"\" is a prefix, so it must be followed by a mnemonic.\nMnemonics it supports are: "+StringUtil.toCommaSpaceSeparatedList(allowedMnemonics)));
				}
			}
			else {
				Mnemonic mnemonic = (Mnemonic)references[primaryKeyword];
				boolean isValidMnemonic = false;
				for (Object[] operands : prefixObject.getOperandTypes(currentMode)) {
					if (operands!=null && operands.length==1 && operands[0].equals(mnemonic.getName().toString())) {
						isValidMnemonic = true;
						break;
					}
				}
				if (!isValidMnemonic) {
					ArrayList<String> allowedMnemonics = new ArrayList<String>();
					for (Object[] operandSet : prefixObject.getOperandTypes(currentMode)) {
						allowedMnemonics.add((String)operandSet[0]);
					}
					addError(new SyntaxError(this,referenceIndices[prefix],getTokenColumn(prefix+1),"\""+references[prefix]+"\" isn't a valid prefix for the mnemonic \""+mnemonic+"\".\nMnemonics that \""+references[prefix]+"\" supports are:"+StringUtil.toCommaSpaceSeparatedList(allowedMnemonics)));
				}
			}
		}
		if (primaryKeyword>=0) {
			// Check for errors involving the mnemonic
			if (references[primaryKeyword] instanceof Mnemonic) {
				Mnemonic mnemonic = (Mnemonic)references[primaryKeyword];

				ArrayList<Object> operands = getOperands();
				boolean errored = false;
				for (Object o : operands) {
					if (o instanceof SyntaxError) {
						addError((SyntaxError)o);
						errored = true;
					}
				}

				if (!errored) {
					int[] opTypes = getOperandTypes(operands,currentMode);
					ArrayList<Object[]> allowedOpTypes = mnemonic.getOperandTypes(currentMode);
					if (!mnemonic.getName().toString().equals("invoke")) {
						int minNumOperands = mnemonic.getMinNumOperands();
						int maxNumOperands = mnemonic.getMaxNumOperands();
						// First check for if the number of operands is more than the maximum number for the mnemonic
						if (opTypes.length>maxNumOperands) {
							addError(new SyntaxError(this, referenceIndices[operandIndices.get(maxNumOperands)],getTokenColumn(operandsEndIndex),"The mnemonic "+mnemonic.getName()+" takes "+((maxNumOperands==minNumOperands)?"exactly ":"at most ")+maxNumOperands+" operands, and you've supplied "+opTypes.length+" operands."));
						}
						// If number of operands is less than or equal to a correct number of operands, do type checking
						else {
							boolean foundTypeMatch = false;
							boolean foundTypeMatchWithSameSize = false;
							Object[] closestMatch = null;
							Object[] exactMatch = null;
							for (Object[] allowedOpType : allowedOpTypes) {
								if (allowedOpType.length>=opTypes.length) {	// Could still have more operands than some allowed number
									boolean tempFoundTypeMatch = true;
									for (int j=0;j<opTypes.length && tempFoundTypeMatch;++j) {
										// Check for incompatibility, and if incompatible, check for special case
										if (allowedOpType[j] instanceof Integer) {
											tempFoundTypeMatch = DataTypeUtil.isCompatibleType(opTypes[j],(Integer)allowedOpType[j]);
										}
										else {	// allowedOpType[j] instanceof String
											tempFoundTypeMatch = operands.get(j).toString().equalsIgnoreCase((String)allowedOpType[j]);
										}
									}
									if (tempFoundTypeMatch) {
										foundTypeMatch = true;
										if (allowedOpType.length==opTypes.length) {
											foundTypeMatchWithSameSize = true;
											exactMatch = allowedOpType;
											break;
										}
										else if (closestMatch==null || allowedOpType.length<closestMatch.length) {
											closestMatch = allowedOpType;
										}
									}
								}
							}
							if (!foundTypeMatch) {
								StringBuffer buf = new StringBuffer("The mnemonic "+mnemonic.getName()+" doesn't accept types \"");
								for (Object opType : opTypes) {
									if (opType instanceof Integer) {
										String name = DataTypeUtil.TYPE_MAP[(Integer)opType].name;
										if (name.contains("_")) {
											name = name.substring(0,name.indexOf("_"));
										}
										buf.append(name).append(',');
									}
									else { // opType instanceof String
										buf.append(opType).append(',');
									}
								}
								buf.setLength(buf.length()-1);
								if (maxNumOperands==minNumOperands || opTypes.length==maxNumOperands) {
									buf.append("\" for its operands.");
								}
								else {
									buf.append("\" for its first ").append(opTypes.length).append(" operands.");
								}
								addError(new SyntaxError(this, getTokenColumn(operandIndices.isEmpty() ? (operandsEndIndex-1) : operandIndices.get(0)),getTokenColumn(operandsEndIndex),buf.toString()));
							}
							// If everything else is okay about the operand types, make sure that there are enough
							else if (!foundTypeMatchWithSameSize) {
								StringBuffer buf = new StringBuffer("The mnemonic "+mnemonic.getName()+" needs "+((maxNumOperands==minNumOperands)?"exactly ":"at least ")+(closestMatch.length-opTypes.length)+" more operands.");
								addError(new SyntaxError(this, getTokenColumn(operandsEndIndex),getTokenColumn(operandsEndIndex),buf.toString()));
							}
							// Check for case of ambiguous size of memory operand
							else if (opTypes.length==2 && (opTypes[0]==DataTypeUtil.TYPE_INDEX_MUNKNOWN_32 || opTypes[0]==DataTypeUtil.TYPE_INDEX_MUNKNOWN_64) && DataTypeUtil.isImmediateType(opTypes[1]) ) {
								addError(new SyntaxError(this, referenceIndices[operandIndices.get(0)],getTokenColumn(operandsEndIndex),"You have to specify the size of the memory operand (e.g. appending \"dword ptr\"), because the size can't necessarily be determined from the immediate (constant-value) operand."));
							}
							else if (opTypes.length==1 && (opTypes[0]==DataTypeUtil.TYPE_INDEX_MUNKNOWN_32 || opTypes[0]==DataTypeUtil.TYPE_INDEX_MUNKNOWN_64)) {
								if (!(exactMatch[0] instanceof Integer) || !(((Integer)exactMatch[0])==DataTypeUtil.TYPE_INDEX_M_32 || ((Integer)exactMatch[0])==DataTypeUtil.TYPE_INDEX_M_64 || ((Integer)exactMatch[0])==DataTypeUtil.TYPE_INDEX_M)) {
									addError(new SyntaxError(this, referenceIndices[operandIndices.get(0)],getTokenColumn(operandsEndIndex),"You have to specify the size of the memory operand (e.g. appending \"dword ptr\"), because the size can't be determined automatically."));
								}
							}
						}
					}
					else {
						// Lastly, check for the correct number of parameters and the correct sizes of parameters
						if (opTypes.length<1 || !DataTypeUtil.isFunctionType(opTypes[0])) {
							addError(new SyntaxError(this, getTokenColumn(primaryKeyword+1),getTokenColumn(primaryKeyword+1),"\"invoke\" is used for calling functions, so its first operand must be a function."));
						}
						else {
							Function calledFunction = (Function)operands.get(0);
							// Next, check for parameters of a type that is too big
							String[] requiredParamTypes = calledFunction.getParameterTypes();
							ArrayList<Integer> requiredParamSizes = new ArrayList<Integer>(requiredParamTypes.length);
							for (String paramType : requiredParamTypes) {
								if (!paramType.equals(Function.AUTOMATIC_PARAM_TYPE_TEXT)) {
									DataType dataType = DataTypeUtil.getDataType(paramType,calledFunction.getLanguage().getScope(),calledFunction.getGlobalScope(),calledFunction.getMode());
									if (dataType!=null) {
										requiredParamSizes.add(dataType.getSize());
									}
									else {
										requiredParamSizes.add(-1);
									}
								}
							}
							// Next, check to see if too many or too few parameters are passed
							int numParamsRequired = requiredParamSizes.size();
							if (opTypes.length!=numParamsRequired+1) {
								StringBuffer buf = new StringBuffer();
								String[] requiredParamNames = calledFunction.getParameterNames();
								for (int j=0;j<requiredParamTypes.length;++j) {
									if (!requiredParamTypes[j].equals(Function.AUTOMATIC_PARAM_TYPE_TEXT)) {
										buf.append(requiredParamNames[j]).append(':').append(requiredParamTypes[j]).append(',');
									}
								}
								if (buf.length()>0) {
									buf.setLength(buf.length()-1);
								}
								if (opTypes.length>numParamsRequired+1) {
									addError(new SyntaxError(this, getTokenColumn(operandIndices.get(numParamsRequired+1)),getTokenColumn(operandsEndIndex),"The function "+calledFunction.getName()+" only accepts "+numParamsRequired+" parameters, and you've supplied "+(opTypes.length-1)+".\nParameters: "+buf.toString()));
								}
								else { // opTypes.length<numParamsRequired+1
									addError(new SyntaxError(this, getTokenColumn(operandsEndIndex),getTokenColumn(operandsEndIndex),"The function "+calledFunction.getName()+" requires "+numParamsRequired+" parameters, and you've only supplied "+(opTypes.length-1)+".\nParameters: "+buf.toString()));
								}
							}
							// TODO: It'll have to eventually do some sort of size check, but since most things when pushed are upsized to DWORDs anyway, I don't know what it makes sense to do here
						}
					}
				}
			}
			// Check for errors involving the keyword macro
			else if (references[primaryKeyword] instanceof Keyword) {
				Keyword keyword = (Keyword)references[primaryKeyword];
				if (keyword==ASM.ASSUME) {
					if (operandIndices.size()<1) {
						addError(new SyntaxError(this, getTokenColumn(primaryKeyword+1),getTokenColumn(primaryKeyword+1),"\"assume\" is used for specifying assumptions about the types being pointed to by registers, but you haven't specified any of these assumptions."));
					}
					// TODO: Also check for multiple assumptions about the same register, and for assuming something to "NOTHING" when it wasn't assumed to something before
					for (int j=0;j<operandIndices.size();++j) {
						int startIndex = operandIndices.get(j);
						int endIndex = (j+1<operandIndices.size()) ? operandIndices.get(j+1)-1 : operandsEndIndex;
						int operandLength = endIndex-startIndex;
						if (operandLength<1) {
							addError(new SyntaxError(this, getTokenColumn(startIndex),getTokenColumn(startIndex),"This is an empty operand.  You might want to put something here, and by \"might want to\" I mean \"must\", and by \"something\" I mean \"a statement of assumption about a register\"."));
						}
						else if (!(references[startIndex] instanceof Register)) {
							addError(new SyntaxError(this, getTokenColumn(startIndex),getTokenColumn(startIndex+1),"You can only make assumptions about registers, and \""+references[startIndex]+"\" is not a register."));
						}
						else if (!DataTypeUtil.isAddressReg((Register)references[startIndex],currentMode)) {
							addError(new SyntaxError(this, getTokenColumn(startIndex),getTokenColumn(startIndex+1),"You can only make assumptions about registers that can be used as addresses."));
						}
						else if (operandLength<2) {
							addError(new SyntaxError(this, getTokenColumn(startIndex),getTokenColumn(startIndex+1),"To make an assumption about a register, the register must be followed by \":PTR MyStructure\" to assume it points to MyStructure, and followed by \":NOTHING\" to remove previous assumptions."));
						}
						else if (!isOperator(startIndex+1,':')) {
							addError(new SyntaxError(this, getTokenColumn(startIndex+1),getTokenColumn(startIndex+2),"To make an assumption about a register, the register must be followed by \":PTR MyStructure\" to assume it points to MyStructure, and followed by \":NOTHING\" to remove previous assumptions."));
						}
						else if (operandLength<3) {
							addError(new SyntaxError(this, getTokenColumn(startIndex),getTokenColumn(startIndex+1),"To make an assumption about a register, the register must be followed by \":PTR MyStructure\" to assume it points to MyStructure, and followed by \":NOTHING\" to remove previous assumptions."));
						}
						else if (references[startIndex+2]!=ASM.PTR && references[startIndex+2]!=ASM.NOTHING) {
							addError(new SyntaxError(this, getTokenColumn(startIndex+2),getTokenColumn(startIndex+3),"To make an assumption about a register, the register must be followed by \":PTR MyStructure\" to assume it points to MyStructure, and followed by \":NOTHING\" to remove previous assumptions."));
						}
						else if (references[startIndex+2]==ASM.PTR) {
							if (operandLength<5 || references[startIndex+3]!=WHITE_SPACE_MARKER) {
								addError(new SyntaxError(this, getTokenColumn(startIndex),getTokenColumn(startIndex+1),"To make an assumption about a register, the register must be followed by \":PTR MyStructure\" to assume it points to MyStructure, and followed by \":NOTHING\" to remove previous assumptions."));
							}
							else if (!(references[startIndex+4] instanceof DataType)) {
								addError(new SyntaxError(this, getTokenColumn(startIndex+4),getTokenColumn(startIndex+5),"This is not a data type, and you can only assume that registers point to data types."));
							}
							else if (operandLength>5) {
								addError(new SyntaxError(this, getTokenColumn(startIndex+5),getTokenColumn(startIndex+6),"To make an assumption about a register, the register must be followed by \":PTR MyStructure\" to assume it points to MyStructure, and followed by \":NOTHING\" to remove previous assumptions."));
							}
						}
						else { // tokens.get(startIndex+2).getObject()==ASM.NOTHING
							if (operandLength>3) {
								addError(new SyntaxError(this, getTokenColumn(startIndex+5),getTokenColumn(startIndex+6),"To make an assumption about a register, the register must be followed by \":PTR MyStructure\" to assume it points to MyStructure, and followed by \":NOTHING\" to remove previous assumptions."));
							}
						}
					}
				}
			}
		}
	}

	public boolean isImmediateStart(int index) {
		return (references[index]==OPERATOR_MARKER && !isOperator(index,'[')) || references[index]==STRING_LITERAL_MARKER || references[index] instanceof Number || references[index] instanceof Directive || references[index] instanceof Constant;
	}

	public boolean isMemoryStart(int index) {
		return isOperator(index,'[') ||
				references[index] instanceof DataType ||
				references[index] instanceof GlobalVariable ||
				references[index] instanceof LocalVariable ||
				(references.length>index+1 && references[index] instanceof Register && ((Register)references[index]).getType()==DataTypeUtil.TYPE_INDEX_SR && isOperator(index+1,':'));
	}

	/**
	 * @return an {@link ArrayList} of objects representing the operands of the line, if applicable
	 */
	public ArrayList<Object> getOperands() {
		// Only applicable to lines with a mnemonic
		if (primaryKeyword>=0/* && references[primaryKeyword] instanceof Mnemonic*/) {
			ArrayList<Object> operands = new ArrayList<Object>(3);
			for (int j=0;j<operandIndices.size();++j) {
				int startIndex = operandIndices.get(j);
				int endIndex;
				if (j+1==operandIndices.size()) {
					endIndex = (commentStart>=0) ? commentStart : nReferences;
				}
				else {
					endIndex = operandIndices.get(j+1)-1;	// -1 is for that the index points to after the comma instead of at it
				}

				// Trim the whitespace from the operand
				while (startIndex<endIndex && references[startIndex]==WHITE_SPACE_MARKER) {
					++startIndex;
				}
				while (endIndex>startIndex && references[endIndex-1]==WHITE_SPACE_MARKER) {
					--endIndex;
				}

				if (startIndex==endIndex) {
					int startColumn = referenceIndices[operandIndices.get(j)-1];
					operands.add(new SyntaxError(this, startColumn,startColumn+1,"This is an empty operand, which isn't much use, and the assembler won't like it much."));
					continue;
				}

				if (isImmediateStart(startIndex)) {
					if (endIndex==startIndex+1 && references[startIndex] instanceof Constant && ((Constant)references[startIndex]).isFunctionConstant()) {
						operands.add(((Constant)references[startIndex]).getValue().getReferences()[0]);
					}
					else {
						Object immediate = ASMParse.instance.immediateShunting(this,startIndex,endIndex);
						if (immediate!=null) {
							if (immediate instanceof SyntaxError) {
								operands.add(immediate);
							}
							else if (immediate instanceof Immediate) {
								operands.add(immediate);
							}
						}
					}
				}
				else if (isMemoryStart(startIndex)) {
					Object operand = memoryShunting(startIndex,endIndex);
					if (operand!=null) {
						// Add it to the list regardless fo whether it is a SyntaxError or not
						operands.add(operand);
					}
				}
				else {
					Object o = references[startIndex];
					if (o instanceof Register || o instanceof Function || o instanceof LineLabel) {
						if ((endIndex-startIndex)!=1) {
							if (o instanceof Register){
								operands.add(new SyntaxError(this, getTokenColumn(startIndex),getTokenColumn(startIndex+1),"Registers can't appear together with anything else in an operand (other than in memory operands)."));
							}
							else if (o instanceof Function) {
								operands.add(new SyntaxError(this, getTokenColumn(startIndex),getTokenColumn(startIndex+1),"Functions can't appear together with anything else in an operand."));
							}
							else { // o instanceof LineLabel
								operands.add(new SyntaxError(this, getTokenColumn(startIndex),getTokenColumn(startIndex+1),"Line labels can't appear together with anything else in an operand."));
							}
						}
						else {
							operands.add(o);
						}
					}
					else if (o==ASM.AT_F) {
						if ((endIndex-startIndex)!=1) {
							operands.add(new SyntaxError(this, getTokenColumn(startIndex),getTokenColumn(startIndex+1),"@F can't appear together with anything else in an operand."));
						}
						else {
							int thisLineNum = list.indexOf(this);
							boolean isFound = false;
							if (thisLineNum>=0) {
								for (int i=thisLineNum+1;i<list.size();++i) {
									if (((ASMLine)list.get(i)).hasLabel() && ((ASMLine)list.get(i)).getLabel().toString().equals(ASM.AT_AT.toString())) {
										operands.add(((ASMLine)list.get(i)).getLabel());
										isFound = true;
										break;
									}
								}
							}
							if (!isFound) {
								operands.add(new SyntaxError(this, getTokenColumn(startIndex),getTokenColumn(startIndex+1),"There is no \"@@:\" line label following this line, so @F isn't valid."));
							}
						}
					}
					else if (o==ASM.AT_B) {
						if ((endIndex-startIndex)!=1) {
							operands.add(new SyntaxError(this, getTokenColumn(startIndex),getTokenColumn(startIndex+1),"@B can't appear together with anything else in an operand."));
						}
						else {
							int thisLineNum = list.indexOf(this);
							boolean isFound = false;
							if (thisLineNum>=0) {
								for (int i=thisLineNum;i>=0;--i) {
									if (((ASMLine)list.get(i)).hasLabel() && ((ASMLine)list.get(i)).getLabel()!=null && ((ASMLine)list.get(i)).getLabel().toString().equals(ASM.AT_AT.toString())) {
										operands.add(((ASMLine)list.get(i)).getLabel());
										isFound = true;
										break;
									}
								}
							}
							if (!isFound) {
								operands.add(new SyntaxError(this, getTokenColumn(startIndex),getTokenColumn(startIndex+1),"There is no \"@@:\" line label on or before this line, so @B isn't valid."));
							}
						}
					}
					else if (o==ASM.HERE) {
						if ((endIndex-startIndex)!=1) {
							operands.add(new SyntaxError(this, getTokenColumn(startIndex),getTokenColumn(startIndex+1),"$ can't yet appear together with anything else in an operand.\nSorry, it's not a high priority unless I get a lot of requests for it."));
						}
						else {
							operands.add(o);
						}
					}
					else {
						operands.add(new SyntaxError(this, getTokenColumn(startIndex),getTokenColumn(startIndex+1),"Things of type \""+((o!=null)?(o.getClass().getName()):"null")+"\", such as \""+o+"\", can't be at the beginning of operands, since it doesn't make any sense."));
					}
				}
			}
			return operands;
		}
		return null;
	}

	public int[] getOperandTypes(int currentMode) {
		return getOperandTypes(getOperands(),currentMode);
	}
	public static int[] getOperandTypes(ArrayList<Object> operands,int currentMode) {
		int[] opTypes = new int[operands.size()];
		for (int opIndex=0;opIndex<operands.size();++opIndex) {
			opTypes[opIndex] = DataTypeUtil.getOperandType(operands.get(opIndex),currentMode);
		}
		return opTypes;
	}

	/**
	 * Parses a memory operand
	 * @param startIndex	the starting index (inclusive) of the memory operand
	 * @param endIndex		the ending index (exclusive) of the memory operand
	 * @return a {@link MemoryOperand} if the operand is valid, or {@link SyntaxError} if there is an error in the syntax
	 */
	private Object memoryShunting(int startIndex, int endIndex) {
		if (startIndex>=endIndex) {
			return null;
		}

		// Do the check for unknown Tokens first to avoid checking if Token objects are null.
		int[] newTokens = new int[endIndex-startIndex];
		int numTokens = 0;
		for (int i=startIndex;i<endIndex;++i) {
			if (references[i]==null) {
				return null;
			}
			if (references[i]!=WHITE_SPACE_MARKER) {
				newTokens[numTokens++] = i;
			}
		}

		int index = 0;
		DataType currentType = null;

		DataType typeOverride = null;
		Symbol variable = null;
		Register regularIndexReg = null;
		Register multipliedIndexReg = null;
		int multiplier = 1;
		Immediate offset = new Immediate(0,null,null);

		Scope languageScope = ASM.language.getScope();
		Scope scope = list.getParent().getScope();
		int mode = list.getParent().getMode();

		if (references[newTokens[0]] instanceof DataType) {
			if (numTokens<2 || references[newTokens[1]]!=ASM.PTR) {
				return new SyntaxError(this, getTokenColumn(newTokens[0]),getTokenColumn(newTokens[0]+1),"Data types at the beginning of an operand only make sense as size overrides, for which they must be followed by \"ptr\".\nExample:\nmov ax,word ptr MyDword");
			}
			typeOverride = (DataType)references[newTokens[0]];
			index += 2;
			if (numTokens<3) {
				return new SyntaxError(this, getTokenColumn(newTokens[0]),getTokenColumn(newTokens[1]+1),"Data type overrides must be followed by a regular memory operand whose type is to be overridden.\nExample:\nmov ax,word ptr MyDword");
			}
		}
		// Ignore any segment prefix, since there can only be one memory operand anyway, and the prefix is handled separately
		if (index+1<numTokens && references[newTokens[index]] instanceof Register && ((Register)references[newTokens[index]]).getType()==DataTypeUtil.TYPE_INDEX_SR && isOperator(newTokens[index+1],':')) {
			index += 2;
			if (numTokens<=index) {
				return new SyntaxError(this, getTokenColumn(newTokens[0]),getTokenColumn(newTokens[1]+1),"Segment overrides must be followed by a regular memory operand.\nExample:\nmov ax,word ptr fs:[bx]");
			}
		}

		Object object = references[newTokens[index]];
		if (!isOperator(newTokens[index],'[') && !(object instanceof GlobalVariable) && !(object instanceof LocalVariable)) {
			return new SyntaxError(this, getTokenColumn(newTokens[index]),getTokenColumn(newTokens[index]+1),"Memory operands (excluding the type override) must start with \"[\" or a variable name.");
		}

		if (object instanceof GlobalVariable) {
			variable = (Symbol)object;
			GlobalVariable globalVar = (GlobalVariable)object;
			// NOTE: scope.getParent() is used instead of scope because somehow there was a deadlock at one point involving something responding to an addition to global scope and then trying to access function scope.
			//       This doesn't solve that problem, but it makes deadlocks less likely in the future.
			currentType = DataTypeUtil.getDataType(globalVar.getDefaultType(),(globalVar.getLanguage()!=null) ? globalVar.getLanguage().getScope() : languageScope,scope.getParent(),globalVar.getMode());
			offset.relocation = new Encoder.Relocation(this,variable);
			++index;
		}
		else if (object instanceof LocalVariable) {
			variable = (Symbol)object;
			currentType = DataTypeUtil.getDataType(((LocalVariable)object).getType().toString(),languageScope,scope,list.getParent().getMode());
			++index;
		}

		while (index<numTokens) {
			if (!isOperator(newTokens[index],'[') && !isOperator(newTokens[index],'.')) {
				if (variable==null) {
					return new SyntaxError(this, getTokenColumn(newTokens[index]),getTokenColumn(newTokens[index]+1),"A variable name or \"[\" should be here.");
				}
				else {
					return new SyntaxError(this, getTokenColumn(newTokens[index]),getTokenColumn(newTokens[index]+1),"\"[\" or \".\" (or nothing) should follow the variable name of a memory operand.");
				}
			}

			if (isOperator(newTokens[index],'[')) {
				int start = index+1;
				int end = index+1;
				while (end<numTokens && !isOperator(newTokens[end],']')) {
					++end;
				}
				if (end==numTokens) {
					return new SyntaxError(this, getTokenColumn(newTokens[index]),getTokenColumn(newTokens[index]+1),"This square bracket has no closing bracket.");
				}
				if (end==start) {
					return new SyntaxError(this, getTokenColumn(newTokens[index]),getTokenColumn(newTokens[index]+1),"Square brackets must contain a register or an immediate (constant-value) expression.");
				}
				while (index+1<end) {
					++index;
					object = references[newTokens[index]];
					if (!(object instanceof Register) && !isImmediateStart(newTokens[index])) {
						return new SyntaxError(this, getTokenColumn(newTokens[index]),getTokenColumn(newTokens[index]+1),"Only registers and immediate (constant-value) expressions can appear inside the square brackets.");
					}
					if (object instanceof Register) {
						Register register = (Register)object;
						if (!DataTypeUtil.isAddressReg(register,mode)) {
							return new SyntaxError(this,getTokenColumn(newTokens[index]),getTokenColumn(newTokens[index]+1),"The register "+register+" can't be used for indexing in "+mode+"-bit mode.");
						}
						if (index+1==end || isOperator(newTokens[index+1],'+') || isOperator(newTokens[index+1],'-')) {
							if (multipliedIndexReg==null) {
								multipliedIndexReg = register;
							}
							else if (regularIndexReg==null) {
								regularIndexReg = register;
							}
							else {
								return new SyntaxError(this,getTokenColumn(newTokens[index]),getTokenColumn(newTokens[index]+1),"Only up to 2 registers can be used together for indexing: one as is and one multiplied by 1, 2, 4, or 8.");
							}

							if (assumptions.containsKey(register)) {
								currentType = assumptions.get(register);
							}

							if (index+1!=end && isOperator(newTokens[index+1],'+')) {
								// Move index to the "+" operator, which skips over it when looping.
								// This is because unlike the "-" operator, registers can be added,
								// though "+" at the beginning of an immediate is handled as well as "-" is.
								++index;
								if (index+1==end) {
									int startColumn = getTokenColumn(newTokens[index]+1);
									return new SyntaxError(this,startColumn,startColumn,"The \"+\" operator here must be followed by a register for indexing or an immediate (constant-value) expression.");
								}
							}
						}
						else if (isOperator(newTokens[index+1],'*')) {
							if (multipliedIndexReg==null) {
								multipliedIndexReg = register;
							}
							else if (regularIndexReg==null && multiplier==1) {
								regularIndexReg = multipliedIndexReg;
								multipliedIndexReg = register;
							}
							else {
								return new SyntaxError(this, getTokenColumn(newTokens[index]),getTokenColumn(newTokens[index]+1),"Only up to 1 index register can have a multiplier in a single instruction: one index register can be as is and one can be multiplied by 1, 2, 4, or 8.");
							}
							++index;
							if (index+1>=end) {
								int startColumn = getTokenColumn(newTokens[index]+1);
								return new SyntaxError(this, startColumn,startColumn,"A register for indexing can be multiplied by 1 (including implicitly), 2, 4, or 8, but you've specified nothing by which to multiply this register.");
							}
							if (!isImmediateStart(newTokens[index+1])) {
								return new SyntaxError(this, getTokenColumn(newTokens[index+1]),getTokenColumn(newTokens[index+1]+1),"A register for indexing can only be multiplied by the constant values 1 (including implicitly), 2, 4, or 8, and this is not a number.");
							}

							if (references[newTokens[index+1]] instanceof Long) {
								long tempMultiplier = ((Long)references[newTokens[index+1]]).longValue();
								if (tempMultiplier==1 || tempMultiplier==2 || tempMultiplier==4 || tempMultiplier==8) {
									multiplier = (int)tempMultiplier;
								}
								else {
									return new SyntaxError(this, getTokenColumn(newTokens[index+1]),getTokenColumn(newTokens[index+1]+1),"A register for indexing can only be multiplied by the constant values 1 (including implicitly), 2, 4, or 8, and you've specified "+tempMultiplier+".");
								}
								++index;
								if (index+1<end) {
									if (isOperator(newTokens[index+1],'+')) {
										// Move index to the "+" operator, which skips over it when looping.
										// This is because unlike the "-" operator, registers can be added,
										// though "+" at the beginning of an immediate is handled as well as "-" is.
										++index;
										if (index+1==end) {
											int startColumn = getTokenColumn(newTokens[index]+1);
											return new SyntaxError(this, startColumn,startColumn,"The \"+\" operator here must be followed by a register for indexing or an immediate (constant-value) expression.");
										}
									}
									else if (!isOperator(newTokens[index+1],'-')) {
										return new SyntaxError(this, getTokenColumn(newTokens[index+1]),getTokenColumn(newTokens[index+1]+1),"Only \"+\" and \"-\" can appear immediately following the multiplier of an index register.");
									}
								}
							}
							else {
								int multiplierStart = index+1;
								int multiplierEnd = index+1;
								int bracketCount = 0;
								boolean isMinusUnary = true;
								do {
									object = references[newTokens[multiplierEnd]];
									if (isOperator(newTokens[multiplierEnd],'(')) {
										++bracketCount;
									}
									else if (isOperator(newTokens[multiplierEnd],')')) {
										if (bracketCount==0) {
											return new SyntaxError(this, getTokenColumn(newTokens[multiplierEnd]),getTokenColumn(newTokens[multiplierEnd]+1),"This closing parenthesis has no corresponding opening parenthesis.");
										}
										--bracketCount;
									}
									else if (bracketCount==0) {
										if (object instanceof Directive && ((Directive)object).precedence()<=Directive.MULTIPLICATION_PRECEDENCE) {
											break;
										}
										else if (isOperator(newTokens[multiplierEnd],'*') || isOperator(newTokens[multiplierEnd],'/')) {
											break;
										}
										else if (!isMinusUnary && (isOperator(newTokens[multiplierEnd],'+') || isOperator(newTokens[multiplierEnd],'-'))) {
											break;
										}
									}

									isMinusUnary = (object==OPERATOR_MARKER && !isOperator(newTokens[multiplierEnd],')')) || object instanceof Directive;
									++multiplierEnd;
								} while (multiplierEnd<end);
								Object multiplierObject = ASMParse.instance.immediateShunting(this,newTokens[multiplierStart],newTokens[multiplierEnd]);
								if (multiplierObject instanceof Immediate) {
									long factor = ((Immediate)multiplierObject).constant;
									if (((Immediate)multiplierObject).relocation!=null || ((Immediate)multiplierObject).diffRelocation!=null) {
										return new SyntaxError(this, getTokenColumn(newTokens[multiplierStart]),getTokenColumn(newTokens[multiplierEnd]),"A register for indexing can only be multiplied by the constant values 1 (including implicitly), 2, 4, or 8.  This multiplier contains offsets, which aren't supported.");
									}
									else if (factor==1 || factor==2 || factor==4 || factor==8) {
										multiplier = (int)factor;
									}
									else {
										return new SyntaxError(this, getTokenColumn(newTokens[multiplierStart]),getTokenColumn(newTokens[multiplierEnd]),"A register for indexing can only be multiplied by the constant values 1 (including implicitly), 2, 4, or 8.  This multiplier evaluates to "+factor+".");
									}
								}
								else if (multiplierObject instanceof SyntaxError) {
									return multiplierObject;
								}
								else {
									return new SyntaxError(this, getTokenColumn(newTokens[multiplierStart]),getTokenColumn(newTokens[multiplierEnd]),"A register for indexing can only be multiplied by the constant values 1 (including implicitly), 2, 4, or 8.  This multiplier is not a constant integer value.");
								}
								if (multiplierEnd<end && isOperator(newTokens[multiplierEnd],'+')) {
									// Move index to the "+" operator, which skips over it when looping.
									// This is because unlike the "-" operator, registers can be added,
									// though "+" at the beginning of an immediate is handled as well as "-" is.
									index = multiplierEnd;
									if (index+1==end) {
										int startColumn = getTokenColumn(newTokens[multiplierEnd]+1);
										return new SyntaxError(this, startColumn,startColumn,"The \"+\" operator here must be followed by a register for indexing or an immediate (constant-value) expression.");
									}
								}
								else if (multiplierEnd>=end) {
									// Finish the loop with index pointing at the last element in the square brackets
									index = multiplierEnd-1;
								}
								else if (isOperator(newTokens[multiplierEnd],'-')) {
									// Don't skip over the "-"
									index = multiplierEnd-1;
								}
								else {
									return new SyntaxError(this, getTokenColumn(newTokens[multiplierEnd]),getTokenColumn(newTokens[multiplierEnd]+1),"Only \"+\" and \"-\" can appear immediately following the multiplier of an index register.");
								}
							}
						}
						else {
							return new SyntaxError(this, getTokenColumn(newTokens[index+1]),getTokenColumn(newTokens[index+1]+1),"Only \"+\", \"-\", and \"*\" can appear immediately following a register inside square brackets.");
						}
					}
					else { // start of immediate expression
						int immediateStart = index;
						int immediateEnd = index;
						int bracketCount = 0;
						boolean isMinusUnary = true;
						do {
							object = references[immediateEnd];
							if (isOperator(newTokens[immediateEnd],'(')) {
								++bracketCount;
							}
							else if (isOperator(newTokens[immediateEnd],')')) {
								if (bracketCount==0) {
									return new SyntaxError(this, getTokenColumn(newTokens[immediateEnd]),getTokenColumn(newTokens[immediateEnd]+1),"This closing parenthesis has no corresponding opening parenthesis.");
								}
								--bracketCount;
							}
							else if (bracketCount==0) {
								if (object instanceof Directive && ((Directive)object).precedence()<=Directive.ADDITION_PRECEDENCE) {
									break;
								}
								else if (!isMinusUnary && (isOperator(newTokens[immediateEnd],'+') || isOperator(newTokens[immediateEnd],'-'))) {
									break;
								}
							}

							isMinusUnary = (object==OPERATOR_MARKER && !isOperator(newTokens[immediateEnd],')')) || object instanceof Directive;
							++immediateEnd;
						} while (immediateEnd<end);
						Object immediateObject = ASMParse.instance.immediateShunting(this,newTokens[immediateStart],newTokens[immediateEnd]);
						if (immediateObject instanceof Immediate) {
							offset.constant += ((Immediate)immediateObject).constant;
							if (offset.relocation==null) {
								offset.relocation = ((Immediate)immediateObject).relocation;
							}
							else if (((Immediate)immediateObject).relocation!=null) {
								return new SyntaxError(this, getTokenColumn(newTokens[immediateStart]),getTokenColumn(newTokens[immediateEnd]),"Only up to one absolute offset (e.g. \"offset MyVariable\") is supported in a single immediate value.");
							}
							if (offset.diffRelocation==null) {
								offset.diffRelocation = ((Immediate)immediateObject).diffRelocation;
							}
							else if (((Immediate)immediateObject).diffRelocation!=null) {
								return new SyntaxError(this, getTokenColumn(newTokens[immediateStart]),getTokenColumn(newTokens[immediateEnd]),"Only up to one difference of offsets (e.g. \"offset EndOfVariables - offset StartOfVariables\") is supported in a single immediate value.");
							}
						}
						else if (immediateObject instanceof SyntaxError) {
							return immediateObject;
						}
						else {
							return new SyntaxError(this, getTokenColumn(newTokens[immediateStart]),getTokenColumn(newTokens[immediateEnd]),"Only constant integer values and addresses can be used in offsets (apart from index registers), and this isn't a constant integer value or an address.");
						}
						if (immediateEnd<end && isOperator(newTokens[immediateEnd],'+')) {
							// Move index to the "+" operator, which skips over it when looping.
							// This is because unlike the "-" operator, registers can be added,
							// though "+" at the beginning of an immediate is handled as well as "-" is.
							index = immediateEnd;
							if (index+1==end) {
								int startColumn = getTokenColumn(newTokens[immediateEnd]+1);
								return new SyntaxError(this, startColumn,startColumn,"The \"+\" operator here must be followed by a register for indexing or an immediate (constant-value) expression.");
							}
						}
						else if (immediateEnd>=end) {
							// Finish the loop with index pointing at the last element in the square brackets
							index = immediateEnd-1;
						}
						else if (isOperator(newTokens[immediateEnd],'-')) {
							// Don't skip over the "-"
							index = immediateEnd-1;
						}
						else {
							return new SyntaxError(this, getTokenColumn(newTokens[immediateEnd]),getTokenColumn(newTokens[immediateEnd]+1),"Only \"+\" and \"-\" can appear immediately following the multiplier of an index register.");
						}
					}
				}
				// Skip from the last element in the square brackets to after "]"
				index+=2;
			}
			else if (isOperator(newTokens[index],'.')) {
				if (index+1>=numTokens || (!(references[newTokens[index+1]] instanceof Structure) && !(references[newTokens[index+1]] instanceof StructureMember))) {
					if (currentType!=null && currentType instanceof Structure) {
						return new SyntaxError(this, getTokenColumn(newTokens[index]+1),getTokenColumn(newTokens[index]+2),"A structure name or member of the structure \""+currentType+"\" must follow \".\".");
					}
					else {
						return new SyntaxError(this, getTokenColumn(newTokens[index]+1),getTokenColumn(newTokens[index]+2),"A structure name must follow \".\".");
					}
				}
				++index;
				object = references[newTokens[index]];
				if (object instanceof Structure) {
					currentType = (DataType)object;
				}
				else { // object instanceof StructureMember
					// NOTE: Since parsing in ASMParse.tokenizeLine() and ASMLine.reassessTokens() require a Structure type before a StructureMember, currentType must be a Structure here if object is a StructureMember
					// However, if there's even a slight difference in parsing, it may be null, so check it.
					if (currentType!=null) {
						offset.constant += ((Structure)currentType).getMemberOffset((StructureMember)object);
						currentType = DataTypeUtil.getDataType(((StructureMember)object).getType().toString(),((Structure)currentType).getLanguage().getScope(),scope,((Structure)currentType).getMode());
					}
				}
				// Move to after this token
				++index;
			}
		}
		return new MemoryOperand((typeOverride==null)?currentType:typeOverride, (LocalVariable)((variable instanceof LocalVariable)?variable:null),regularIndexReg,multipliedIndexReg,multiplier,offset);
	}

	public boolean reassessOnAdd(Symbol symbol) {
		// If the symbol doesn't have a name yet (whenever adding something completely new), the line doesn't need to be updated
		if (symbol.getName().length()==0) {
			return false;
		}
		return reassessTokens();
	}

	public boolean reassessOnRemove(Symbol symbol) {
		return reassessTokens();
	}

	public boolean reassessOnChange(SymbolChangeEvent event) {
		boolean changed = reassessTokens();
		if (event instanceof SymbolRenameEvent) {
			// Don't need any extra processing for this
		}
		else if (event instanceof SymbolTypeChangeEvent) {
			// Don't need any extra processing for this
		}
		else if (event instanceof ParameterAddedEvent) {
			// TODO: Consider putting in filler text for parameter if the function is used.  However, then it wouldn't be the inverse of removing a parameter, since it doesn't remove the parameter text.
		}
		else if (event instanceof ParameterRemovedEvent) {
			// TODO: Don't remove the text, because the parameter removal must be undoable, but somehow flag it if used.
		}
		else if (event instanceof ParameterMovedEvent) {
			// TODO: Move the supplied parameter if the function is used.
		}
		else if (event instanceof MemberAddedEvent) {
			// Don't need any extra processing for this
		}
		else if (event instanceof MemberRemovedEvent) {
			// Don't need any extra processing for this
		}
		else if (event instanceof ValueChangeEvent) {
			// Don't need any extra processing for this
		}
		else {
			throw new NotImplementedException();
		}
		// updateErrors() is now always called in reassessTokens(), so it doesn't need to be called here
//		updateErrors();
		return changed;
	}

	public boolean reassessAssumptionsOnAdd(HashMap<Register, DataType> newAssumptions, Symbol symbol) {
		return reassessAssumptions(newAssumptions);
		// FIXME: Implement this!!!!;
	}
	public boolean reassessAssumptionsOnRemove(HashMap<Register, DataType> newAssumptions, Symbol symbol) {
		return reassessAssumptions(newAssumptions);
		// FIXME: Implement this!!!!;
	}

	public boolean reassessAssumptionsOnChange(HashMap<Register, DataType> newAssumptions, SymbolChangeEvent e) {
		return reassessAssumptions(newAssumptions);
		// FIXME: Implement this!!!!;
	}

	public boolean reassessAssumptions(HashMap<Register, DataType> newAssumptions) {
		if (!isAssumeLine()) {
			assumptions = newAssumptions;
		}
		boolean changed = reassessTokens();
		if (isAssumeLine()) {
			assumptions.clear();
			assumptions.putAll(newAssumptions);
			for (int j=primaryKeyword+2;j<nReferences-2;++j) {	// +2 is for that there must be space after ASSUME, -2 is for that EAX:NOTHING is 3 Tokens
				// Check for <Register>:NOTHING or <Register>:PTR <Structure>
				if (references[j] instanceof Register && isOperator(j+1,':')) {
					if (references[j+2]==ASM.NOTHING) {
						assumptions.remove(references[j]);
					}
					else if (references[j+2]==ASM.PTR && (j+4<nReferences) && references[j+3]==WHITE_SPACE_MARKER && (references[j+4] instanceof DataType)) {
						assumptions.put((Register)references[j],(DataType)references[j+4]);
					}
				}
			}
		}
		return changed;
	}

	/**
	 * Adjusts the {@link Token}s for changes to {@link Symbol}s on this {@link ASMLine}
	 *
	 * NOTE: This assumes that no changes to language scope have been made.
	 *
	 * @return true iff any {@link Token}s were changed
	 */
	public boolean reassessTokens() {
		// Don't reassess the dummy line
		if (list==null) {
			return false;
		}
		boolean changed = false;
		int index = 0;
		Scope languageScope = ASM.language.getScope();
		Scope scope = list.getParent().getScope();
		int mode = list.getParent().getMode();
		Structure currentStructureType = null;
		Structure temporaryStructureType = null;
		Object lastMajorRef = null;	// latest previous reference that isn't whitespace and isn't a dot
		while (index<nReferences) {
			// Skip over whitespace
			while (index<nReferences && references[index]==WHITE_SPACE_MARKER) {
				++index;
			}
			if (index>=nReferences) {
				break;
			}
			if (references[index]==OPERATOR_MARKER) {
				if (isOperator(index,',')) {
					currentStructureType = null;
					temporaryStructureType = null;
				}
				else if (isOperator(index,';')) {
					break;
				}
			}
			else {
				Structure activeStructure = (temporaryStructureType!=null) ? temporaryStructureType : currentStructureType;
				Object object = references[index];
				// If the object no longer exists, set it to null
				if (object!=null && (
						(activeStructure==null && (object instanceof StructureMember))
						|| (activeStructure!=null && (object instanceof StructureMember) && !activeStructure.getScope().contains((Symbol)object))
						// NOTE: The case of a line label "@@" is handled separately, because it's a copy of something in language scope, so it has a different ID and gets missed otherwise.
						|| (!(object instanceof Symbol && languageScope.contains((Symbol)object)) && !(object instanceof StructureMember) && (object instanceof Symbol) && scope!=null && !scope.contains((Symbol)object) && !(object instanceof LineLabel && ((LineLabel)object).getName().equals(ASM.AT_AT.getName()))))) {
					references[index] = null;
					object = null;
					changed = true;
				}
				String tokenText = new String(text,referenceIndices[index],getTokenColumn(index+1)-referenceIndices[index]);
				// If the object is null, check if there's something by the name
				if (object==null) {
					if (activeStructure!=null) {
						ArrayList<Symbol> symbols = activeStructure.getScope().get(tokenText);
						if (symbols!=null && symbols.size()>0) {
							Symbol symbol = symbols.get(0);
							references[index] = symbol;
							if (Scope.isBeingRenamed(symbol)) {
								Scope.addRenameReference(symbol, new TextCaret(null,list,list.indexOf(this),index,0));
							}
//							System.out.println(symbols.get(0)+":"+((StructureMember)symbols.get(0)).getType());
							changed = true;
						}
					}
					if (references[index]==null) {
						ArrayList<Symbol> symbols = (scope!=null) ? scope.get(tokenText) : null;
						if (symbols!=null && symbols.size()>0) {
							Symbol symbol = symbols.get(0);
							references[index] = symbol;
							if (Scope.isBeingRenamed(symbol)) {
								Scope.addRenameReference(symbol, new TextCaret(null,list,list.indexOf(this),index,0));
							}
							changed = true;
						}
					}
					object = references[index];
				}
				// If the object isn't null, check for change in structure type and update currentStructureType if needed
				if (object!=null) {
					if ((object instanceof Symbol) && !(object instanceof Keyword)) {
						if (!((Symbol)object).getName().equals(tokenText)) {
							replaceJustText(referenceIndices[index],getTokenColumn(index+1),((Symbol)object).getName().toCharArray());
							changed = true;
						}
						if (activeStructure!=null) {
							ArrayList<Symbol> symbols = activeStructure.getScope().get(tokenText);
							if (symbols!=null && symbols.size()>0 && !symbols.contains(object)) {
								references[index] = symbols.get(0);
								changed = true;
							}
						}

						if (object instanceof Structure) {
							if (lastMajorRef!=null && (lastMajorRef==Directive.OFFSET || lastMajorRef==Directive.SIZEOF)) {
								temporaryStructureType = (Structure)object;
							}
							else {
								currentStructureType = (Structure)object;
							}
						}
						else if ((object instanceof LocalVariable) || (object instanceof GlobalVariable) || (object instanceof StructureMember)) {
							DataType type;
							if (object instanceof LocalVariable) {
								type = DataTypeUtil.getDataType(((LocalVariable)object).getType().toString(),languageScope,scope,mode);
							}
							else if (object instanceof GlobalVariable) {
								GlobalVariable variable = (GlobalVariable)object;
								type = DataTypeUtil.getDataType(variable.getDefaultType(),(variable.getLanguage()!=null && variable.getLanguage()!=null) ? variable.getLanguage().getScope() : languageScope,scope,variable.getMode());
							}
							else { // object instanceof StructureMember
								type = DataTypeUtil.getDataType(((StructureMember)object).getType().toString(),(activeStructure!=null && activeStructure.getLanguage()!=null) ? activeStructure.getLanguage().getScope() : languageScope,scope,(activeStructure!=null) ? activeStructure.getMode() : mode);
							}
							if (type instanceof Structure) {
								if (temporaryStructureType!=null || (lastMajorRef!=null && (lastMajorRef==Directive.OFFSET || lastMajorRef==Directive.SIZEOF))) {
									temporaryStructureType = (Structure)type;
								}
								else {
									currentStructureType = (Structure)type;
								}
							}
							else {
								if (temporaryStructureType!=null) {
									temporaryStructureType = null;
								}
								else if (!(lastMajorRef!=null && (lastMajorRef==Directive.OFFSET || lastMajorRef==Directive.SIZEOF))) {
									currentStructureType = null;
								}
							}
						}
					}
					// If we have something like "VECTOR ptr [eax].x", the ".x" is invalid if eax has no assumptions.
					// This is because the typecast is only the final data type, not the current one.
					// "[eax].VECTOR.x" would get element x of structure VECTOR.
					else if (object==ASM.PTR && currentStructureType!=null) {
						currentStructureType = null;
					}

					if ((object instanceof Register) && assumptions.containsKey(object)) {
						DataType type = assumptions.get(object);
						currentStructureType = (type instanceof Structure) ? (Structure)type : null;
					}
				}
			}
			if (!isOperator(index,'.')) {
				lastMajorRef = references[index];
				if (!(lastMajorRef instanceof LocalVariable || lastMajorRef instanceof GlobalVariable || lastMajorRef instanceof Structure || lastMajorRef instanceof StructureMember)) {
					temporaryStructureType = null;
				}
			}
			++index;
		}
		// TODO: Find a way to updateErrors() more efficiently, because changes to global scope in large projects could get very slow
		// The only cases found so far in which updateErrors() must be called when no changes occurred are when
		// another line's line label is added or removed having the same name as this line's line label.
		// In both of those cases, the function is redrawn anyway, so returning false is still okay.
//		if (changed) {
			updateErrors();
//		}
		return changed;
	}

	/**
	 * This determines the operand types and encoding pattern for this line, given operands already parsed.
	 * @param operands the operands whose to match against allowed types
	 * @param mode the current CPU mode
	 * @return the operand types and encoding of this line if a match is found, else null
	 */
	public Pair<Object[],short[]> getOperandTypesAndEncoding(ArrayList<Object> operands, int mode) {
		// Check for errors involving the mnemonic
		if (references[primaryKeyword] instanceof Mnemonic) {
			Mnemonic mnemonic = (Mnemonic)references[primaryKeyword];
			int[] opTypes = getOperandTypes(operands,mode);
			ArrayList<Object[]> allowedOpTypes = mnemonic.getOperandTypes(mode);
			int minNumOperands = mnemonic.getMinNumOperands();
			int maxNumOperands = mnemonic.getMaxNumOperands();
			// First check for if the number of operands is more than the maximum number for the mnemonic
			if (opTypes.length>maxNumOperands) {
				addError(new SyntaxError(this, getTokenColumn(operandIndices.get(maxNumOperands)),getTokenColumn(operandsEndIndex+1),"The mnemonic "+mnemonic.getName()+" takes "+((maxNumOperands==minNumOperands)?"exactly ":"at most ")+maxNumOperands+" operands, and you've supplied "+opTypes.length+" operands."));
			}
			// If number of operands is less than or equal to a correct number of operands, do type checking
			else {
				boolean foundTypeMatch = false;
				boolean foundTypeMatchWithSameSize = false;
				Object[] closestMatch = null;
				int closestMatchIndex = -1;
				for (int i = 0; i < allowedOpTypes.size(); i++) {
					Object[] allowedOpType = allowedOpTypes.get(i);
					if (allowedOpType.length >= opTypes.length) {	// Could still have more operands than some allowed number
						boolean tempFoundTypeMatch = true;
						for (int j = 0; j < opTypes.length && tempFoundTypeMatch; ++j) {
							// Check for incompatibility, and if incompatible, check for special case
							if (allowedOpType[j] instanceof Integer) {
								tempFoundTypeMatch = DataTypeUtil.isCompatibleType(opTypes[j], (Integer)allowedOpType[j]);
							}
							else {	// allowedOpType[j] instanceof String
								tempFoundTypeMatch = operands.get(j).toString().equalsIgnoreCase((String)allowedOpType[j]);
							}
						}
						if (tempFoundTypeMatch) {
							foundTypeMatch = true;
							if (allowedOpType.length==opTypes.length) {
								foundTypeMatchWithSameSize = true;
								closestMatch = allowedOpType;
								closestMatchIndex = i;
								break;
							}
							else if (closestMatch==null || allowedOpType.length < closestMatch.length) {
								closestMatch = allowedOpType;
								closestMatchIndex = i;
							}
						}
					}
				}
				if (!foundTypeMatch) {
					StringBuffer buf = new StringBuffer("The mnemonic "+mnemonic.getName()+" doesn't accept types \"");
					for (Object opType : opTypes) {
						if (opType instanceof Integer) {
							String name = DataTypeUtil.TYPE_MAP[(Integer)opType].name;
							if (name.contains("_")) {
								name = name.substring(0,name.indexOf("_"));
							}
							buf.append(name).append(',');
						}
						else { // opType instanceof String
							buf.append(opType).append(',');
						}
					}
					buf.setLength(buf.length()-1);
					if (maxNumOperands==minNumOperands || opTypes.length==maxNumOperands) {
						buf.append("\" for its operands.");
					}
					else {
						buf.append("\" for its first ").append(opTypes.length).append(" operands.");
					}
					addError(new SyntaxError(this, getTokenColumn(operandIndices.get(0)),getTokenColumn(operandsEndIndex),buf.toString()));
				}
				// If everything else is okay about the operand types, make sure that there are enough
				else if (!foundTypeMatchWithSameSize) {
					StringBuffer buf = new StringBuffer("The mnemonic "+mnemonic.getName()+" needs "+((maxNumOperands==minNumOperands)?"exactly ":"at least ")+(closestMatch.length-opTypes.length)+" more operands.");// Closest type match: \"");
//								for (Object type : closestMatch) {
//									if (type instanceof Integer) {
//										buf.append(DataTypeUtil.TYPE_MAP[(Integer)type].name).append(',');
//									}
//									else { // opType instanceof String
//										buf.append(type).append(',');
//									}
//								}
//								buf.setLength(buf.length()-1);
//								buf.append("\".");
					addError(new SyntaxError(this, getTokenColumn(operandsEndIndex),getTokenColumn(operandsEndIndex),buf.toString()));
				}
				// Check for case of ambiguous size of memory operand
				else if (opTypes.length==2 && (opTypes[0]==DataTypeUtil.TYPE_INDEX_MUNKNOWN_32 || opTypes[0]==DataTypeUtil.TYPE_INDEX_MUNKNOWN_64) && DataTypeUtil.isImmediateType(opTypes[1]) ) {
					addError(new SyntaxError(this, getTokenColumn(operandIndices.get(0)),getTokenColumn(operandsEndIndex),"You have to specify the size of the memory operand (e.g. appending \"dword ptr\"), because the size can't necessarily be determined from the immediate (constant-value) operand."));
				}
				else if (opTypes.length==1 && (opTypes[0]==DataTypeUtil.TYPE_INDEX_MUNKNOWN_32 || opTypes[0]==DataTypeUtil.TYPE_INDEX_MUNKNOWN_64)) {
					// Check to see if it's actually ambiguous
					int size = (closestMatch[0] instanceof Integer) ? DataTypeUtil.TYPE_MAP[(Integer)closestMatch[0]].size : DataTypeUtil.TypeMapping.UNKNOWN_SIZE;
					boolean isError = false;
					for (Object[] otherOpTypes : mnemonic.getOperandTypes(mode)) {
						if (otherOpTypes.length==1 && otherOpTypes[0] instanceof Integer && DataTypeUtil.TYPE_MAP[(Integer)otherOpTypes[0]].size!=size) {
							addError(new SyntaxError(this, getTokenColumn(operandIndices.get(0)),getTokenColumn(operandsEndIndex),"You have to specify the size of the memory operand (e.g. appending \"dword ptr\"), because the size can't be determined automatically."));
							isError = true;
							break;
						}
					}
					if (!isError) {
						return new Pair<Object[],short[]>(closestMatch,mnemonic.getEncodings(mode).get(closestMatchIndex));
					}
				}
				else {
					return new Pair<Object[],short[]>(closestMatch,mnemonic.getEncodings(mode).get(closestMatchIndex));
				}
			}
		}
		return null;
	}
}
